1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 
12 module hip.systems.game;
13 import hip.global.gamedef;
14 import hip.hipaudio.audio;
15 import hip.view;
16 import hip.error.handler;
17 import hip.api.view.scene;
18 
19 import hip.systems.timer_manager;
20 import hip.event.dispatcher;
21 import hip.event.handlers.keyboard;
22 import hip.event.handlers.mouse;
23 import hip.windowing.events;
24 import hip.hiprenderer.renderer;
25 import hip.graphics.g2d.renderer2d;
26 public import hip.event.handlers.input_listener;
27 
28 version(WebAssembly) version = CustomRuntime;
29 version(CustomRuntimeTest) version = CustomRuntime;
30 version(PSVita) version = CustomRuntime;
31 
32 version(Load_DScript)
33 {
34     import hip.systems.hotload;
35     import hip.systems.compilewatcher;
36     private bool getDubError(string line)
37     {
38         import hip.console.log;
39         import hip.util.string:indexOf, lastIndexOf;
40         int errInd = line.indexOf("Warning:");
41         if(errInd == -1) errInd = line.indexOf("Error:"); //Check first for warnings
42         return errInd != -1;
43     }
44     extern(System) AScene function() HipremeEngineGameInit;
45     extern(System) void function() HipremeEngineGameDestroy;
46 }
47 
48 
49 class GameSystem
50 {
51     /** 
52      * Holds the member that generates the events as inputs
53      */
54     EventDispatcher dispatcher;
55     AScene[] scenes;
56 
57     HipInputListener inputListener;
58     HipInputListener scriptInputListener;
59     string projectDir, buildCommand = "dub build --parallel --skip-registry=all";
60     ///Resets delta time after a reload for not jumping frames.
61     protected bool shouldResetDelta;
62     protected __gshared AScene externalScene;
63 
64     version(Load_DScript)
65     {
66         static CompileWatcher watcher;
67         protected static HotloadableDLL hotload;
68     }
69     bool hasFinished;
70     float fps = 0;
71     float targetFPS = 0;
72     float fpsAccumulator = 0;
73     size_t frames = 0;
74 
75     this(float targetFPS)
76     {
77         this.targetFPS = targetFPS;
78         dispatcher = new EventDispatcher(HipRenderer.window);
79         dispatcher.addOnResizeListener((uint width, uint height)
80         {
81             HipRenderer.width  = width;
82             HipRenderer.height = height;
83             resizeRenderer2D(width, height);
84             foreach (AScene s; scenes)
85                 s.onResize(width, height);
86         });
87         inputListener = new HipInputListener(dispatcher);
88         scriptInputListener = new HipInputListener(dispatcher);
89         inputListener.addKeyboardListener(HipKey.ESCAPE, 
90             (meta){hasFinished = true;}
91         );
92         inputListener.addKeyboardListener(HipKey.F1, 
93             (meta){import hip.bind.interpreters; reloadInterpreter();},
94             HipButtonType.up
95         );
96 
97         version(Load_DScript)
98         {
99             inputListener.addKeyboardListener(HipKey.F5,(meta)
100                 {
101                     import hip.console.log;
102                     rawlog("Recompiling and Reloading game ");
103                     recompileReloadExternalScene();
104                 }, HipButtonType.up
105             );
106         }
107         
108     }
109 
110     void loadGame(string gameDll, string buildCommand)
111     {
112         version(Load_DScript)
113         {
114             import hip.filesystem.hipfs;
115             import hip.util.path;
116             import hip.util.system;
117             import hip.util.string:indexOf;
118             import hip.console.log;
119 
120 
121             if(gameDll.isAbsolutePath && HipFS.absoluteIsFile(gameDll))
122             {
123                 projectDir = gameDll.dirName;
124             }
125             else if(!gameDll.extension && gameDll.indexOf("projects/") == -1)
126             {
127                 projectDir = joinPath("projects", gameDll);
128                 gameDll = joinPath("projects", gameDll, gameDll);
129             }
130             else
131                 projectDir = gameDll;
132 
133             watcher = new CompileWatcher(projectDir, null, ["d"]).run;
134             this.buildCommand = buildCommand ? buildCommand : this.buildCommand;
135 
136             hotload = new HotloadableDLL(gameDll, (void* lib)
137             {
138                 ErrorHandler.assertLazyExit(lib != null, "No library " ~ gameDll ~ " was found");
139                 HipremeEngineGameInit = 
140                     cast(typeof(HipremeEngineGameInit))
141                     dynamicLibrarySymbolLink(lib, "HipremeEngineGameInit");
142                 ErrorHandler.assertLazyExit(HipremeEngineGameInit != null,
143                 "HipremeEngineGameInit wasn't found when looking into "~gameDll);
144                 HipremeEngineGameDestroy = 
145                     cast(typeof(HipremeEngineGameDestroy))
146                     dynamicLibrarySymbolLink(lib, "HipremeEngineGameDestroy");
147                 ErrorHandler.assertLazyExit(HipremeEngineGameDestroy != null,
148                 "HipremeEngineGameDestroy wasn't found when looking into "~gameDll);
149             });
150         }
151     }
152 
153     void recompileGame()
154     {
155         version(Load_DScript)
156         {
157             import std.process:pipeShell, Redirect, wait;
158             import hip.console.log;
159             auto dub = pipeShell("cd "~projectDir~" && "~buildCommand, Redirect.stderrToStdout | Redirect.stdout);
160 
161             scope string[] errors;
162             foreach(l; dub.stdout.byLine)
163             {
164                 if(getDubError(cast(string)l))
165                     errors~= l.idup;
166                 else logln(cast(string)l);
167             }
168             int status = wait(dub.pid);
169             //2 == up to date
170             if(errors.length) 
171             {
172                 loglnError(errors);
173                 foreach(err; errors) loglnError(err);
174             }
175             else
176                 hotload.reload();
177         }
178     }
179 
180     void startGame()
181     {
182         version(Test)
183         {
184             // addScene(new SoundTestScene());
185             // addScene(new ChainTestScene());
186             // addScene(new AssetTest());
187             import hip.view.testscene;
188             import hip.console.log;
189             import hip.api.data.commons;
190             mixin LoadReferencedAssets!(["hip.view.testscene"]);
191             loadReferenced;
192             hiplog("starting test scene.");
193             addScene(new TestScene());
194         }
195         else version(Load_DScript)
196         {
197             ErrorHandler.assertExit(HipremeEngineGameInit != null, "No game was loaded");
198             externalScene = HipremeEngineGameInit();
199             addScene(externalScene);
200         }
201         else version(Standalone)
202         {
203             import gamescript.entry;
204             externalScene = HipremeEngineMainScene();
205             addScene(externalScene);
206         }
207     }
208 
209     void recompileReloadExternalScene()
210     {
211         version(Load_DScript)
212         {
213             import hip.util.array:remove;
214             import hip.console.log;
215             if(hotload)
216             {
217                 shouldResetDelta = true;
218                 rawlog("Recompiling game");
219                 HipTimerManager.clearSchedule();
220                 scriptInputListener.clearAll();
221                 HipremeEngineGameDestroy();
222                 scenes.remove(externalScene);
223                 externalScene = null;
224                 recompileGame(); // Calls hotload.reload();
225                 startGame();
226             }
227         }
228     }
229 
230     /**
231     *   Adding a scene will initialize them, while checking for assets referencing for auto loading them.
232     */
233     void addScene(AScene s)
234     {
235         import hip.assetmanager;
236         HipAssetManager.addOnLoadingFinish(()
237         {
238             import hip.console.log;
239             version(CustomRuntime)
240             {
241                 s.preload();
242                 loglnWarn("Initializing scene ", s.getName);
243                 s.initialize();
244                 scenes~= s;
245             }
246             else
247             {
248                 try{
249                     s.preload();
250                     loglnWarn("Initializing scene ", s.getName);
251                     s.initialize();
252                     scenes~= s;
253                 }
254                 catch (Error e){scriptFatalError(e);}
255             }
256         });
257     }
258 
259     bool update(float deltaTime)
260     {
261         import hip.assetmanager;
262         import std.stdio;
263         frames++;
264         fpsAccumulator+= deltaTime;
265         if(shouldResetDelta)
266         {
267             deltaTime = 0;
268             shouldResetDelta = false;
269         }
270         if(fpsAccumulator >= 1.0)
271         {
272             import hip.console.log;
273             // logln("FPS: ", frames);
274             frames = 0;
275             fpsAccumulator = 0;
276         }
277         HipAudio.update();
278         HipTimerManager.update(deltaTime);
279         HipAssetManager.update();
280         
281         version(Load_DScript)
282         {
283             if(watcher.update())
284                 recompileReloadExternalScene();
285         }
286         dispatcher.handleEvent();
287         dispatcher.pollGamepads(deltaTime);
288         inputListener.update();
289         scriptInputListener.update();
290 
291         if(hasFinished || dispatcher.hasQuit)
292             return false;
293         foreach(s; scenes)
294         {
295             import hip.console.log;
296             version(CustomRuntime)
297             {
298                 if(s is null) logln("SCENE IS NULL");
299                 else s.update(deltaTime);
300             }
301             else
302             {
303                 try
304                 {
305                     if(s is null) logln("SCENE IS NULL");
306                     else s.update(deltaTime);
307                 }
308                 catch (Error e){scriptFatalError(e);}
309             }
310         }
311 
312         return true;
313     }
314 
315     version(CustomRuntime){}
316     else
317     void scriptFatalError(Throwable e, string file = __FILE__, size_t line = __LINE__, string func = __PRETTY_FUNCTION__)
318     {
319         import hip.console.log;
320         import hip.util.path;
321         loglnError(e.msg, ". Project: (", projectDir, ") at file (", e.file, ":",e.line, ")");
322         quit();
323         ErrorHandler.assertExit(false, "Script Fatal Error", file, line, __MODULE__, func);
324     }
325     void render()
326     {
327         version(CustomRuntime)
328         {
329             foreach (AScene s; scenes)
330                 s.render();
331         }
332         else
333         {
334             try
335             {
336                 foreach (AScene s; scenes)
337                     s.render();
338             }
339             catch(Throwable e){scriptFatalError(e);}
340         }
341         HipTimerManager.render();
342     }
343     void postUpdate()
344     {
345         dispatcher.postUpdate();
346     }
347     
348     void quit()
349     {
350         version(Load_DScript)
351         {
352             if(hotload !is null)
353             {
354                 if(HipremeEngineGameDestroy != null)
355                     HipremeEngineGameDestroy();
356                 scenes.length = 0;
357                 externalScene = null;
358                 hotload.dispose();
359                 watcher.stop();
360             }
361         }
362         import hip.assetmanager;
363         HipAssetManager.dispose();
364  
365     }
366 }